use std::sync::Arc;

use serde::Deserialize;
use tokio::sync::Mutex;
use tracing::{error, info};

use bc_base::{
    self, PublicKeyBytes, PublicKeyHashBytes, TransactionId, TransactionValue,
    constants::{COINBASE_TX_ID_OF_GENESIS_BLOCK, GENESIS_ACCOUNT, GENESIS_ACCOUNT_HASH},
};
use bc_consensus::{Consensus, ConsensusError};
use bc_lib::{
    mining::MiningManager,
    network::BcNetworkManager,
    top_storage::{
        BlockAndIndexStorageWithCache, MiscItemStorageWithCache, TopStorageError,
        TransactionAndIndexStorageWithCache,
    },
    transaction,
};
use bc_p2p::Multiaddr;
use bc_wallet::{Wallet, WalletsError};

pub use bc_consensus::LocalTransaction;
pub use process_p2p_user_event::start_process_p2p_user_event_task;

#[derive(thiserror::Error, Debug)]
pub enum BcManagerError {
    /// An error occurred while interacting with the database.
    #[error("Database error: {0}")]
    DatabaseError(String),
    /// An error occurred while interacting with the blockchain.
    #[error("Blockchain error: {0}")]
    BlockchainError(String),

    #[error("Wallet error: {0}")]
    WalletsError(#[from] WalletsError),

    #[error("Consensus error: {0}")]
    ConsensusError(#[from] ConsensusError),

    #[error("TopStorageError error: {0}")]
    TopStorageError(#[from] TopStorageError),

    #[error("Account not found: {0}")]
    AccountNotFound(PublicKeyHashBytes),

    #[error("Insufficient balance: {0}")]
    InsufficientBalance(PublicKeyHashBytes, TransactionValue),

    #[error("BcLibNetWorkError error: {0}")]
    BcLibNetWorkError(#[from] bc_lib::network::BcLibNetWorkError),
}

#[cfg(not(debug_assertions))]
pub type BcManagerMutex = Mutex<BcManager>;
#[cfg(debug_assertions)]
pub type BcManagerMutex = bc_util::logged_mutex::LoggedMutex<BcManager>;

pub type BcManagerHandler = Arc<BcManagerMutex>;

mod for_block_and_tx;
mod for_mining;
mod for_p2p_network;
mod for_wallet;
mod process_p2p_user_event;

#[derive(Debug, Deserialize, Clone)]
pub struct KeyPairStr {
    pub secret: String,
    pub public: String,
}

#[derive(Deserialize)]
pub struct SendTransactionRequest {
    // payer: PublicKeyHashBytes, // Hex string representing the sender's public key
    payee: PublicKeyHashBytes,
    amount: TransactionValue,
}

impl SendTransactionRequest {
    pub fn new(
        // payer: PublicKeyHashBytes,
        payee: PublicKeyHashBytes,
        amount: TransactionValue,
    ) -> Self {
        SendTransactionRequest {
            // payer,
            payee,
            amount,
        }
    }
}

#[derive(Debug, Clone)]
pub struct BcManager {
    _db_root_dir: std::path::PathBuf,

    wallet: Arc<Mutex<Wallet>>, // store account and utxos
    consensus: Arc<Mutex<Consensus>>,
    p2p_network: Arc<Mutex<BcNetworkManager>>,
    mining_manager: Arc<Mutex<MiningManager>>,

    block_and_index_storage_with_cache: Arc<Mutex<BlockAndIndexStorageWithCache>>,
    tx_and_index_storage_with_cache: Arc<Mutex<TransactionAndIndexStorageWithCache>>,
    misc_item_storage_with_cache: Arc<Mutex<MiscItemStorageWithCache>>,

    gen_block_when_receive_tx_flag: bool,
}

impl BcManager {
    pub async fn new(
        db_root_dir: std::path::PathBuf,
        mining_manager: MiningManager,
        secret_key_seed: Option<u8>,
        listen_address: Option<Multiaddr>,
        shutdown_signal: tokio::sync::watch::Receiver<bool>,
    ) -> BcManagerHandler {
        // let bc_block_data = Arc::new(Mutex::new(BlockData::new(&db_root_dir).unwrap()));

        let block_db = Arc::new(Mutex::new(
            BlockAndIndexStorageWithCache::new(&db_root_dir).await,
        ));

        let transaction_db = Arc::new(Mutex::new(
            TransactionAndIndexStorageWithCache::new(&db_root_dir).await,
        ));

        let misc_item_db = Arc::new(Mutex::new(
            MiscItemStorageWithCache::new(&db_root_dir).await,
        ));

        let consensus = Consensus::new(
            block_db.clone(),
            transaction_db.clone(),
            misc_item_db.clone(),
        )
        .await; // load block & tx index
        let consensus = Arc::new(Mutex::new(consensus));
        let wallet = Wallet::new(&db_root_dir, consensus.clone()).await; // load wallet

        let p2p_network =
            BcNetworkManager::new(secret_key_seed, listen_address, shutdown_signal).await;

        let t = Self {
            _db_root_dir: db_root_dir.clone(),

            wallet: Arc::new(Mutex::new(wallet)),

            consensus,

            p2p_network: Arc::new(Mutex::new(p2p_network)),
            mining_manager: Arc::new(Mutex::new(mining_manager)),
            block_and_index_storage_with_cache: block_db,
            tx_and_index_storage_with_cache: transaction_db,
            misc_item_storage_with_cache: misc_item_db,

            gen_block_when_receive_tx_flag: false,
        };

        #[cfg(debug_assertions)]
        let handler = Arc::new(BcManagerMutex::new("BcManager", t));

        #[cfg(not(debug_assertions))]
        let handler = Arc::new(BcManagerMutex::new(t));

        handler
    }

    pub async fn shutdown(&mut self) {
        self.p2p_network.lock().await.shutdown().await;
    }

    pub async fn flush_database(&self) {
        self.wallet
            .lock()
            .await
            .store()
            .await
            .expect("Failed to store wallet");
        self.wallet
            .lock()
            .await
            .flush_database()
            .await
            .expect("Failed to flush wallet to database");

        self.block_and_index_storage_with_cache
            .lock()
            .await
            .flush_block_and_index()
            .await
            .expect("Failed to flush blockchain to database");
        self.tx_and_index_storage_with_cache
            .lock()
            .await
            .flush_tx_and_index()
            .await
            .expect("Failed to flush blockchain to database");
        self.misc_item_storage_with_cache
            .lock()
            .await
            .flush_database()
            .await
            .expect("Failed to flush blockchain to database");
    }
}

impl BcManager {
    pub async fn check_genesis_info(&mut self) {
        info!("Checking genesis information...");
        bc_lib::check_genesis_info_in_bc_storage(
            self.block_and_index_storage_with_cache.clone(),
            self.tx_and_index_storage_with_cache.clone(),
            self.misc_item_storage_with_cache.clone(),
        )
        .await;
    }

    pub async fn check_genesis_info_in_wallets(&mut self) {
        let public_key_hash = PublicKeyHashBytes::from_hex_str(GENESIS_ACCOUNT_HASH).unwrap();
        let _public_key = PublicKeyBytes::from_hex_str(GENESIS_ACCOUNT).unwrap();
        let tx_id = TransactionId::from_hex_str(COINBASE_TX_ID_OF_GENESIS_BLOCK).unwrap();
        let mut wallet = self.wallet.lock().await;

        if wallet.exists_account(&public_key_hash) && !wallet.is_tx_in_wallet(&tx_id) {
            info!("Wallet has genesis account and has no coinbase transaction.");
            tracing::warn!(
                "TODO: Scanning the blockchain for existing accounts and UTXOs is not implemented yet."
            );
            // TODO: Scan the blockchain for existing accounts and UTXOs.

            let tx = transaction::create_coinbase_for_genesis_block();
            wallet.add_tx_if_mine(&tx_id, &tx).await.unwrap();
        }
    }
}

// for test
impl BcManager {
    pub fn get_gen_block_when_receive_tx_flag(&self) -> bool {
        self.gen_block_when_receive_tx_flag
    }

    pub fn set_gen_block_when_receive_tx_flag(&mut self, value: bool) {
        self.gen_block_when_receive_tx_flag = value
    }

    pub async fn dump_all_block(&self) {
        self.consensus.lock().await.dump_all_block().await;
    }

    pub async fn dump_all_txs(&self) {
        self.consensus.lock().await.dump_all_txs().await;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use tokio::sync::watch;

    async fn new_bc_manager(
        db_root_dir: std::path::PathBuf,
        mining_manager: MiningManager,
    ) -> (BcManagerHandler, watch::Sender<bool>) {
        let (shutdown_tx, shutdown_rx) = watch::channel(false);
        let manager = BcManager::new(db_root_dir, mining_manager, None, None, shutdown_rx).await;

        (manager, shutdown_tx)
    }

    #[tokio::test]
    async fn test_send_transaction() {
        let (mining_manager, _mining_cmd_receiver, _transaction_receiver) = MiningManager::new();

        let tmp_dir = bc_util::convert_to_temp_dir("test_db_send_transaction");
        let path = tmp_dir.path().to_path_buf();

        {
            let (manager, shutdown_tx) = new_bc_manager(path, mining_manager).await;
            let request = SendTransactionRequest {
                // payer: PublicKeyHashBytes::from_hex_str(GENESIS_ACCOUNT_HASH).unwrap(),
                payee: PublicKeyHashBytes::from_hex_str("c0b86b8c786e9dc809f1045fba1a099900721568")
                    .unwrap(),
                amount: 100,
            };
            let response = manager.lock().await.send_transaction(request).await;
            println!("{:?}", response);
            shutdown_tx.send(true).unwrap();
            manager.lock().await.shutdown().await;
        }
        tmp_dir.close().unwrap();
    }

    #[tokio::test]
    async fn test_query_balance() {
        let (mining_manager, _mining_cmd_receiver, _transaction_receiver) = MiningManager::new();

        let tmp_dir = bc_util::convert_to_temp_dir("test_db_query_balance");
        let path = tmp_dir.path().to_path_buf();

        {
            let (manager, shutdown_tx) = new_bc_manager(path, mining_manager).await;
            let response = manager.lock().await.query_balance().await;
            println!("{:?}", response);
            shutdown_tx.send(true).unwrap();
            manager.lock().await.shutdown().await;
        }

        tmp_dir.close().unwrap();
    }

    #[tokio::test]
    async fn test_query_transactions() {
        let (mining_manager, _mining_cmd_receiver, _transaction_receiver) = MiningManager::new();

        let tmp_dir = bc_util::convert_to_temp_dir("test_db_query_transactions");
        let path = tmp_dir.path().to_path_buf();

        {
            let (manager, shutdown_tx) = new_bc_manager(path, mining_manager).await;
            let tx_id = TransactionId::from_hex_str(
                "02b97b3c115098f9f258b1fefe70bf5c50efbb051c8b62d5ea65094c043b5b3f",
            )
            .unwrap();
            let response = manager.lock().await.get_transaction(&tx_id).await;
            println!("{:?}", response);
            shutdown_tx.send(true).unwrap();
            manager.lock().await.shutdown().await;
        }

        tmp_dir.close().unwrap();
    }
}
